package cn.portx.hibernate;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.internal.CriteriaImpl;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.bstek.bdf3.dorado.jpa.JpaUtil;
import com.bstek.dorado.data.provider.And;
import com.bstek.dorado.data.provider.Criteria;
import com.bstek.dorado.data.provider.Junction;
import com.bstek.dorado.data.provider.Or;
import com.bstek.dorado.data.provider.Page;
import com.bstek.dorado.data.provider.filter.SingleValueFilterCriterion;

import cn.portx.ICallback;

@Component
public class HibernateDao extends AbstractDao implements ApplicationContextAware {
	private ApplicationContext applicationContext;

	public <T> Session getSession(Class<T> domainClass) {
		Session session = JpaUtil.getEntityManager(domainClass).unwrap(org.hibernate.Session.class);
		return session;
	}

	public Collection<?> query(DetachedCriteria detachedCriteria, Session session) {
		try {
			return detachedCriteria.getExecutableCriteria(session).list();
		} finally {
			this.sessionFlush(session);
			this.sessionClose(session);
		}
	}

	public Collection<?> query(DetachedCriteria detachedCriteria, int pageIndex, int pageSize, Session session) {
		try {
			return detachedCriteria.getExecutableCriteria(session).setFirstResult((pageIndex - 1) * pageSize)
					.setMaxResults(pageSize).list();
		} finally {
			this.sessionFlush(session);
			this.sessionClose(session);
		}
	}

	@SuppressWarnings("rawtypes")
	public int queryCount(DetachedCriteria detachedCriteria, Session session) {
		try {
			org.hibernate.Criteria criteria = detachedCriteria.getExecutableCriteria(session);
			try {
				Field field = CriteriaImpl.class.getDeclaredField("orderEntries");
				field.setAccessible(true);
				field.set(criteria, new ArrayList());
			} catch (Exception e) {
				e.printStackTrace();
			}
			int totalCount = 0;
			Object totalObject = criteria.setProjection(Projections.rowCount()).uniqueResult();
			if (totalObject instanceof Integer) {
				totalCount = (Integer) totalObject;
			} else if (totalObject instanceof Long) {
				totalCount = ((Long) totalObject).intValue();
			} else if (totalObject != null) {
				throw new RuntimeException(
						"Can not convert [" + totalObject + "] to a int value when query entity total count!");
			}
			return totalCount;
		} finally {
			this.sessionFlush(session);
			this.sessionClose(session);
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void pagingQuery(Page<?> page, DetachedCriteria detachedCriteria, Session session) {
		try {
			org.hibernate.Criteria criteria = detachedCriteria.getExecutableCriteria(session);
			CriteriaImpl criteriaImpl = (CriteriaImpl) criteria;
			List orderEntrys = new ArrayList();
			try {
				Field field = CriteriaImpl.class.getDeclaredField("orderEntries");
				field.setAccessible(true);
				orderEntrys = (List) field.get(criteriaImpl);
				field.set(criteria, new ArrayList());
			} catch (Exception e) {
				e.printStackTrace();
			}
			Projection projection = criteriaImpl.getProjection();
			int totalCount = 0;
			Object totalObject = criteria.setProjection(Projections.rowCount()).uniqueResult();
			if (totalObject instanceof Integer) {
				totalCount = (Integer) totalObject;
			} else if (totalObject instanceof Long) {
				totalCount = ((Long) totalObject).intValue();
			} else if (totalObject != null) {
				throw new RuntimeException(
						"Can not convert [" + totalObject + "] to a int value when query entity total count!");
			}
			page.setEntityCount(totalCount);

			try {
				Field field = CriteriaImpl.class.getDeclaredField("orderEntries");
				field.setAccessible(true);
				for (int i = 0; i < orderEntrys.size(); i++) {
					List innerOrderEntries = (List) field.get(criteriaImpl);
					innerOrderEntries.add(orderEntrys.get(i));
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			criteria.setProjection(projection);
			if (projection == null) {
				criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
			}
			int start = (page.getPageNo() - 1) * page.getPageSize();
			page.setEntities(detachedCriteria.getExecutableCriteria(session).setFirstResult(start)
					.setMaxResults(page.getPageSize()).list());
		} finally {
			this.sessionFlush(session);
			this.sessionClose(session);
		}
	}

	public void pagingQuery(Page<?> page, String queryString, String countQueryString, Session session)
			throws Exception {
		this.pagingQuery(page, queryString, countQueryString, null, null, session);
	}

	public List<?> query(String queryString, Map<String, Object> parametersMap, int pageIndex, int pageSize,
			Session session) {
		try {
			Query query = session.createQuery(queryString);
			return query.setFirstResult((pageIndex - 1) * pageSize).setMaxResults(pageSize).list();
		} finally {
			this.sessionFlush(session);
			this.sessionClose(session);
		}
	}

	protected void pagingQuery(final Page<?> page, final String queryString, final String countQueryString,
			final Object[] parameters, final Map<String, Object> parametersMap, Session session) throws Exception {
		this.doInHibernateSession(session, new ISessionCallback<Object>() {
			@SuppressWarnings("unchecked")
			public Object doInSession(Session session) {
				String queryHql = queryString.trim();
				Query query = session.createQuery(queryHql);
				int safePageSize = page.getPageSize() < 1 ? 65535 : page.getPageSize();
				int start = (page.getPageNo() - 1) * safePageSize;
				if (parameters != null) {
					setQueryParameters(query, parameters);
				} else if (parametersMap != null) {
					setQueryParameters(query, parametersMap);
				}
				query.setMaxResults(safePageSize).setFirstResult(start);
				page.setEntities(query.list());
				Query countQuery = session.createQuery(countQueryString);
				if (parameters != null) {
					setQueryParameters(countQuery, parameters);
				} else if (parametersMap != null) {
					setQueryParameters(countQuery, parametersMap);
				}
				int count = 0;
				Object countObj = countQuery.uniqueResult();
				if (countObj instanceof Long) {
					count = ((Long) countObj).intValue();
				} else if (countObj instanceof Integer) {
					count = ((Integer) countObj).intValue();
				}
				page.setEntityCount(count);
				return null;
			}
		});
	}

	/**
	 * 通过Hibernate使用SQL进行分页查询
	 * 
	 * @param page
	 * @param sql
	 * @param countSql
	 * @param parametersMap
	 *            sql需要的参数
	 * @param sqlQueryCallback
	 *            创建SQLQuery对象后调用的回调函数（接收SQLQuery作为参数）
	 * @param resultCallback
	 *            查询返回结果后调用的回调函数（接收一个List对象，该对象就是查询结果集）
	 * @param session
	 */
	public void pagingSQLQuery(final Page<?> page, final String sql, final String countSql,
			final Map<String, Object> parametersMap, final ICallback<Void, SQLQuery> sqlQueryCallback,
			final ICallback<List<?>, List<?>> resultCallback, Session session) {
		this.doInHibernateSession(session, new ISessionCallback<Object>() {
			@SuppressWarnings("unchecked")
			public Object doInSession(Session session) {
				String queryHql = sql.trim();
				SQLQuery query = session.createSQLQuery(queryHql);
				if (sqlQueryCallback != null) {
					sqlQueryCallback.execute(query);
				}
				int safePageSize = page.getPageSize() < 1 ? 65535 : page.getPageSize();
				int start = (page.getPageNo() - 1) * safePageSize;
				if (parametersMap != null) {
					setQueryParameters(query, parametersMap);
				}
				query.setMaxResults(safePageSize).setFirstResult(start);
				List entities = query.list();
				if (resultCallback != null) {
					entities = resultCallback.execute(entities);
				}
				page.setEntities(entities);
				SQLQuery countQuery = session.createSQLQuery(countSql);
				if (parametersMap != null) {
					setQueryParameters(countQuery, parametersMap);
				}
				int count = 0;
				Object countObj = countQuery.uniqueResult();
				if (countObj instanceof Number) {
					count = ((Number) countObj).intValue();
				}
				page.setEntityCount(count);
				return null;
			}
		});
	}

	public void pagingQuery(Page<?> page, String hql, String countHql, Map<String, Object> parametersMap,
			Session session) throws Exception {
		this.pagingQuery(page, hql, countHql, null, parametersMap, session);
	}

	public void setQueryParameters(Query query, Object[] parameters) {
		if (parameters == null)
			return;
		for (int i = 0; i < parameters.length; i++) {
			query.setParameter(i, parameters[i]);
		}
	}

	public void setQueryParameters(Query query, Map<String, Object> parameters) {
		if (parameters == null)
			return;
		for (String name : parameters.keySet()) {
			Object obj = parameters.get(name);
			if (obj instanceof Collection) {
				query.setParameterList(name, (Collection<?>) obj);
			} else if (obj instanceof Object[]) {
				query.setParameterList(name, (Object[]) obj);
			} else {
				query.setParameter(name, obj);
			}
		}
	}

	public <T> List<T> query(String hql, Session session) {
		return this.query(hql, null, null, session);
	}

	public <T> List<T> query(String hql, Map<String, Object> parameterMap, Session session) {
		return this.query(hql, null, parameterMap, session);
	}

	@SuppressWarnings("unchecked")
	private <T> List<T> query(String hql, Object[] parameters, Map<String, Object> parameterMap, Session session) {
		List<T> result = null;
		try {
			Query query = session.createQuery(hql);
			if (parameters != null) {
				this.setQueryParameters(query, parameters);
			} else if (parameterMap != null) {
				this.setQueryParameters(query, parameterMap);
			}
			result = query.list();
		} finally {
			this.sessionClose(session);
		}
		return result;
	}

	public int queryForInt(String hql, Map<String, Object> parameterMap, Session session) {
		return this.queryForInt(hql, null, parameterMap, session);
	}

	private int queryForInt(String hql, Object[] parameters, Map<String, Object> parameterMap, Session session) {
		int count = 0;
		try {
			Query countQuery = session.createQuery(hql);
			if (parameters != null) {
				setQueryParameters(countQuery, parameters);
			} else if (parameterMap != null) {
				setQueryParameters(countQuery, parameterMap);
			}
			Object countObj = countQuery.uniqueResult();
			if (countObj instanceof Long) {
				count = ((Long) countObj).intValue();
			} else if (countObj instanceof Integer) {
				count = ((Integer) countObj).intValue();
			}
		} finally {
			this.sessionClose(session);
		}
		return count;
	}

	public Object doInHibernateSession(Session session, ISessionCallback<?> job) {
		Object result = null;
		try {
			result = job.doInSession(session);
		} finally {
			this.sessionFlush(session);
			this.sessionClose(session);
		}
		return result;
	}

	private void sessionFlush(Session session) {
		session.flush();
	}

	private void sessionClose(Session session) {
		// session.close();//此处不再关闭，交友系统Jpa的EntityManager来自动关闭
	}

	public DetachedCriteria buildDetachedCriteria(Criteria criteria, Class<?> entityClass) {
		return buildDetachedCriteria(criteria, entityClass, null);
	}

	public DetachedCriteria buildDetachedCriteria(Criteria criteria, Class<?> entityClass, String alias) {
		DetachedCriteria dc = null;
		if (StringUtils.isEmpty(alias)) {
			dc = DetachedCriteria.forClass(entityClass);
		} else {
			dc = DetachedCriteria.forClass(entityClass, alias);
		}
		if (criteria != null && criteria.getCriterions().size() > 0) {
			buildCriterions(criteria.getCriterions(), dc);
		}
		return dc;
	}

	private void buildCriterions(Collection<com.bstek.dorado.data.provider.Criterion> criterions, DetachedCriteria dc) {
		for (com.bstek.dorado.data.provider.Criterion c : criterions) {
			if (c instanceof SingleValueFilterCriterion) {
				SingleValueFilterCriterion fc = (SingleValueFilterCriterion) c;
				dc.add(this.buildCriterion(fc));
			}
			if (c instanceof Junction) {
				Junction jun = (Junction) c;
				org.hibernate.criterion.Junction junction = null;
				if (jun instanceof Or) {
					junction = Restrictions.disjunction();
				} else if (jun instanceof And) {
					junction = Restrictions.conjunction();
				}
				Collection<com.bstek.dorado.data.provider.Criterion> subCriterions = jun.getCriterions();
				if (subCriterions != null) {
					buildCriterions(subCriterions, junction);
				}
				dc.add(junction);
			}
		}
	}

	private void buildCriterions(Collection<com.bstek.dorado.data.provider.Criterion> criterions,
			org.hibernate.criterion.Junction dc) {
		for (com.bstek.dorado.data.provider.Criterion c : criterions) {
			if (c instanceof SingleValueFilterCriterion) {
				SingleValueFilterCriterion fc = (SingleValueFilterCriterion) c;
				dc.add(this.buildCriterion(fc));
			}
			if (c instanceof Junction) {
				Junction jun = (Junction) c;
				org.hibernate.criterion.Junction junction = null;
				if (jun instanceof Or) {
					junction = Restrictions.disjunction();
				} else if (jun instanceof And) {
					junction = Restrictions.conjunction();
				}
				Collection<com.bstek.dorado.data.provider.Criterion> subCriterions = jun.getCriterions();
				if (subCriterions != null) {
					buildCriterions(subCriterions, dc);
				}
				dc.add(junction);
			}
		}
	}

	private Criterion buildCriterion(SingleValueFilterCriterion fc) {
		Criterion result = null;
		String operator = buildOperator(fc.getFilterOperator());
		String propertyName = fc.getProperty();
		Property p = Property.forName(propertyName);
		if (operator.equals("like")) {
			result = p.like("%" + fc.getValue() + "%");
		} else if (operator.startsWith("*")) {
			result = p.like((String) fc.getValue(), MatchMode.END);
		} else if (operator.endsWith("*")) {
			result = p.like((String) fc.getValue(), MatchMode.START);
		} else if (operator.equals(">")) {
			result = p.gt(fc.getValue());
		} else if (operator.equals("<")) {
			result = p.lt(fc.getValue());
		} else if (operator.equals(">=")) {
			result = p.ge(fc.getValue());
		} else if (operator.equals("<=")) {
			result = p.le(fc.getValue());
		} else if (operator.equals("=")) {
			result = p.eq(fc.getValue());
		} else if (operator.equals("<>")) {
			result = p.ne(fc.getValue());
		} else {
			throw new IllegalArgumentException("Query operator[" + operator + "] is invalide");
		}
		return result;
	}

	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}
